home *** CD-ROM | disk | FTP | other *** search
Text File | 2008-03-18 | 71.1 KB | 1,882 lines |
- /* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Oracle Corporation code.
- *
- * The Initial Developer of the Original Code is
- * Oracle Corporation
- * Portions created by the Initial Developer are Copyright (C) 2004
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
- * Dan Mosedale <dan.mosedale@oracle.com>
- * Mike Shaver <mike.x.shaver@oracle.com>
- * Gary van der Merwe <garyvdm@gmail.com>
- * Bruno Browning <browning@uwalumni.com>
- * Matthew Willis <lilmatt@mozilla.com>
- * Daniel Boelzle <daniel.boelzle@sun.com>
- * Philipp Kewisch <mozilla@kewis.ch>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
- //
- // calDavCalendar.js
- //
-
- // XXXdmose deal with generation goop
-
- // XXXdmose deal with locking
-
- // XXXdmose need to make and use better error reporting interface for webdav
- // (all uses of aStatusCode, probably)
-
- // XXXdmose use real calendar result codes, not NS_ERROR_FAILURE for everything
-
- const xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>\n';
-
- function calDavCalendar() {
- this.initProviderBase();
- this.unmappedProperties = [];
- this.mUriParams = null;
- this.mItemInfoCache = {};
- this.mDisabled = false;
- this.mCalHomeSet = null;
- this.mPrincipalsNS = null;
- this.mInBoxUrl = null;
- this.mOutBoxUrl = null;
- this.mHaveScheduling = false;
- this.mMailToUrl = null;
- this.mHrefIndex = [];
- this.mAuthScheme = null;
- this.mAuthRealm = null;
- this.mObserver = null;
- }
-
- // some shorthand
- const nsIWebDAVOperationListener =
- Components.interfaces.nsIWebDAVOperationListener;
- const calICalendar = Components.interfaces.calICalendar;
- const nsISupportsCString = Components.interfaces.nsISupportsCString;
- const calIErrors = Components.interfaces.calIErrors;
- const calIFreeBusyInterval = Components.interfaces.calIFreeBusyInterval;
- const calICalDavCalendar = Components.interfaces.calICalDavCalendar;
- const calIDateTime = Components.interfaces.calIDateTime;
-
- var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
- .getService(Components.interfaces.nsIXULAppInfo);
- var isOnBranch = appInfo.platformVersion.indexOf("1.8") == 0;
-
-
- function getLocationPath(item) {
-
- var locPath = this.mItemInfoCache[item.id].locationPath;
- if (locPath) {
- LOG("using locationPath: " + locPath);
- } else {
- locPath = item.id;
- if (locPath) {
- locPath += ".ics";
- }
- LOG("using locationPath: " + locPath);
- }
- return locPath;
- }
-
- // END_OF_TIME needs to be the max value a PRTime can be
- const START_OF_TIME = -0x7fffffffffffffff;
- const END_OF_TIME = 0x7fffffffffffffff;
-
- // used in checking calendar URI for (Cal)DAV-ness
- const kDavResourceTypeNone = 0;
- const kDavResourceTypeCollection = 1;
- const kDavResourceTypeCalendar = 2;
-
- // used for etag checking
- const CALDAV_ADOPT_ITEM = 1;
- const CALDAV_MODIFY_ITEM = 2;
- const CALDAV_DELETE_ITEM = 3;
-
- calDavCalendar.prototype = {
- __proto__: calProviderBase.prototype,
- //
- // nsISupports interface
- //
- QueryInterface: function (aIID) {
- return doQueryInterface(this, calDavCalendar.prototype, aIID,
- [Components.interfaces.calICalendarProvider,
- Components.interfaces.nsIInterfaceRequestor,
- Components.interfaces.calIFreeBusyProvider,
- calICalDavCalendar]);
- },
-
- initMemoryCalendar: function caldav_iMC() {
- this.mMemoryCalendar = Components.classes["@mozilla.org/calendar/calendar;1?type=memory"]
- .createInstance(Components.interfaces.calICalendar);
-
- this.mMemoryCalendar.superCalendar = this;
- this.mObserver = new calDavObserver(this);
- this.mMemoryCalendar.addObserver(this.mObserver);
- this.mMemoryCalendar.setProperty("relaxedMode", true);
- },
-
- //
- // calICalendarProvider interface
- //
- get prefChromeOverlay() {
- return null;
- },
-
- get displayName() {
- return calGetString("calendar", "caldavName");
- },
-
- createCalendar: function caldav_createCal() {
- throw NS_ERROR_NOT_IMPLEMENTED;
- },
-
- deleteCalendar: function caldav_deleteCal(cal, listener) {
- throw NS_ERROR_NOT_IMPLEMENTED;
- },
-
- //
- // calICalendar interface
- //
-
- // readonly attribute AUTF8String type;
- get type() { return "caldav"; },
-
- mDisabled: false,
-
- mPrincipalsNS: null,
-
- mHaveScheduling: false,
-
- mMailToUrl: null,
-
- get canRefresh() {
- return true;
- },
-
- // mUriParams stores trailing ?parameters from the
- // supplied calendar URI. Needed for (at least) Cosmo
- // tickets
- mUriParams: null,
-
- get uri() { return this.mUri },
-
- set uri(aUri) {
- this.mUri = aUri;
- this.initMemoryCalendar();
-
- this.checkDavResourceType();
- return aUri;
- },
-
- get mCalendarUri() {
- calUri = this.mUri.clone();
- var parts = calUri.spec.split('?');
- if (parts.length > 1) {
- calUri.spec = parts.shift();
- this.mUriParams = '?' + parts.join('?');
- }
- if (calUri.spec.charAt(calUri.spec.length-1) != '/') {
- calUri.spec += "/";
- }
- return calUri;
- },
-
- setCalHomeSet: function caldav_setCalHomeSet() {
- var calUri = this.mUri.clone();
- var split1 = calUri.spec.split('?');
- var baseUrl = split1[0];
- if (baseUrl.charAt(baseUrl.length-1) == '/') {
- baseUrl = baseUrl.substring(0, baseUrl.length-2);
- }
- var split2 = baseUrl.split('/');
- split2.pop();
- calUri.spec = split2.join('/') + '/';
- this.mCalHomeSet = calUri;
- },
-
- mOutBoxUrl: null,
-
- mInBoxUrl: null,
-
- mAuthScheme: null,
-
- mAuthRealm: null,
-
- get authRealm() {
- return this.mAuthRealm;
- },
-
- makeUri: function caldav_makeUri(aInsertString) {
- var spec = this.mCalendarUri.spec + aInsertString;
- if (this.mUriParams) {
- return spec + this.mUriParams;
- }
- return spec;
- },
-
- get mLocationPath() {
- return decodeURIComponent(this.mCalendarUri.path);
- },
-
- // XXX todo: in general we want to do CalDAV scheduling, but for servers
- // that don't support it, we want Itip
- // sendItipInvitations is now used from calProviderBase.
-
- promptOverwrite: function caldavPO(aMethod, aItem, aListener, aOldItem) {
- var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
- getService(Components.interfaces.nsIPromptService);
-
- var promptTitle = calGetString("calendar", "itemModifiedOnServerTitle");
- var promptMessage = calGetString("calendar", "itemModifiedOnServer");
- var buttonLabel1;
-
- if (aMethod == CALDAV_MODIFY_ITEM) {
- promptMessage += calGetString("calendar", "modifyWillLoseData");
- buttonLabel1 = calGetString("calendar", "proceedModify");
- } else {
- promptMessage += calGetString("calendar", "deleteWillLoseData");
- buttonLabel1 = calGetString("calendar", "proceedDelete");
- }
-
- var buttonLabel2 = calGetString("calendar", "updateFromServer");
-
- var flags = promptService.BUTTON_TITLE_IS_STRING *
- promptService.BUTTON_POS_0 +
- promptService.BUTTON_TITLE_IS_STRING *
- promptService.BUTTON_POS_1;
-
- var choice = promptService.confirmEx(null, promptTitle, promptMessage,
- flags, buttonLabel1, buttonLabel2,
- null, null, {});
-
- if (choice == 0) {
- if (aMethod == CALDAV_MODIFY_ITEM) {
- this.doModifyItem(aItem, aOldItem, aListener, true);
- } else {
- this.doDeleteItem(aItem, aListener, true);
- }
- } else {
- this.getUpdatedItem(aItem, aListener);
- }
-
- },
-
- mItemInfoCache: null,
-
- mHrefIndex: null,
-
- /**
- * prepare channel with standard request headers
- * and upload data/content-type if needed
- *
- * @param arUri channel Uri
- * @param aUploadData data to be uploaded, if any
- * @param aContentType value for Content-Type header, if any
- */
-
- prepChannel: function caldavPC(aUri, aUploadData, aContentType) {
- var ioService = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService);
- var channel = ioService.newChannelFromURI(aUri);
-
- var httpchannel = channel.QueryInterface(Components.interfaces
- .nsIHttpChannel);
-
- httpchannel.setRequestHeader("Accept", "text/xml", false);
- httpchannel.setRequestHeader("Accept-Charset", "utf-8,*;q=0.1", false);
- httpchannel.notificationCallbacks = this;
-
- if (aUploadData) {
- httpchannel = httpchannel.QueryInterface(Components.interfaces.
- nsIUploadChannel);
- var converter =
- Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
- converter.charset = "UTF-8";
- var stream = converter.convertToInputStream(aUploadData);
- httpchannel.setUploadStream(stream, aContentType, -1);
- }
- return httpchannel;
- },
-
- /**
- * addItem(); required by calICalendar.idl
- * we actually use doAdoptItem()
- *
- * @param aItem item to add
- * @param aListener listener for method completion
- */
-
- addItem: function caldavAI(aItem, aListener) {
- var newItem = aItem.clone();
- return this.doAdoptItem(newItem, aListener, false);
- },
-
- /**
- * adooptItem(); required by calICalendar.idl
- * we actually use doAdoptItem()
- *
- * @param aItem item to check
- * @param aListener listener for method completion
- */
- adoptItem: function caldavAtI(aItem, aListener) {
- var newItem = aItem.clone();
- return this.doAdoptItem(newItem, aListener, false);
- },
-
- /**
- * Performs the actual addition of the item to CalDAV store
- *
- * @param aItem item to add
- * @param aListener listener for method completion
- * @param aIgnoreEtag ignore item etag
- */
- doAdoptItem: function caldavaDAI(aItem, aListener, aIgnoreEtag) {
- if (aItem.id == null && aItem.isMutable) {
- aItem.id = getUUID();
- }
-
- if (aItem.id == null) {
- if (aListener)
- aListener.onOperationComplete (this.superCalendar,
- Components.results.NS_ERROR_FAILURE,
- aListener.ADD,
- aItem.id,
- "Can't set ID on non-mutable item to addItem");
- return;
- }
-
- var locationPath = aItem.id + ".ics";
- var itemUri = this.mCalendarUri.clone();
- itemUri.spec = this.makeUri(locationPath);
- LOG("itemUri.spec = " + itemUri.spec);
-
- var addListener = {};
- var thisCalendar = this;
- addListener.onStreamComplete =
- function onPutComplete(aLoader, aContext, aStatus, aResultLength,
- aResult) {
- var status = aContext.responseStatus;
- // 201 = HTTP "Created"
- //
- if (status == 201) {
- LOG("Item added successfully");
-
- var retVal = Components.results.NS_OK;
- // Some CalDAV servers will modify items on PUT (add X-props,
- // change location, etc) so we'd best re-fetch in order to know
- // the current state of the item
- // Observers will be notified in getUpdatedItem()
- thisCalendar.getUpdatedItem(aItem, aListener);
-
- } else if (status == 200) {
- LOG("CalDAV: 200 received from server: server malfunction");
- retVal = Components.results.NS_ERROR_FAILURE;
- } else if (status == 412) {
- LOG("CalDAV: etag exists on adopt item: server malfunction");
- retVal = Components.results.NS_ERROR_FAILURE;
- } else {
- if (status > 999) {
- status = "0x" + aStatusCode.toString(16);
- }
-
- // XXX real error handling
- LOG("Error adding item: " + status);
- retVal = Components.results.NS_ERROR_FAILURE;
- }
- }
-
- aItem.calendar = this.superCalendar;
- aItem.generation = 1;
-
- // LOG("icalString = " + aItem.icalString);
-
- var httpchannel = this.prepChannel(itemUri, aItem.icalString,
- "text/calendar; charset=utf-8");
-
-
- if (!aIgnoreEtag) {
- httpchannel.setRequestHeader("If-None-Match", "*", false);
- }
-
- var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
- .createInstance(Components.interfaces
- .nsIStreamLoader);
-
- if (isOnBranch) {
- streamLoader.init(httpchannel, addListener, httpchannel);
- } else {
- streamLoader.init(addListener);
- httpchannel.asyncOpen(streamLoader, httpchannel);
- }
-
- return;
- },
-
- /**
- * modifyItem(); required by calICalendar.idl
- * we actually use doModifyItem()
- *
- * @param aItem item to check
- * @param aListener listener for method completion
- */
- modifyItem: function caldavMI(aNewItem, aOldItem, aListener) {
- return this.doModifyItem(aNewItem, aOldItem, aListener, false);
- },
-
- /**
- * Modifies existing item in CalDAV store.
- *
- * @param aItem item to check
- * @param aOldItem previous version of item to be modified
- * @param aListener listener from original request
- * @param aIgnoreEtag ignore item etag
- */
- doModifyItem: function caldavMI(aNewItem, aOldItem, aListener, aIgnoreEtag) {
-
- if (aNewItem.id == null) {
-
- // XXXYYY fix to match iface spec
- // this is definitely an error
- if (aListener) {
- try {
- aListener.onOperationComplete(this.superCalendar,
- Components.results.NS_ERROR_FAILURE,
- aListener.MODIFY,
- aItem.id,
- "ID for modifyItem doesn't exist or is null");
- } catch (ex) {
- LOG("modifyItem's onOperationComplete threw an"
- + " exception " + ex + "; ignoring");
- }
- }
-
- return;
- }
-
- if (aNewItem.parentItem != aNewItem) {
- aNewItem.parentItem.recurrenceInfo.modifyException(aNewItem);
- aNewItem = aNewItem.parentItem;
- }
-
- var eventUri = this.mCalendarUri.clone();
- eventUri.spec = this.makeUri(this.mItemInfoCache[aNewItem.id].locationPath);
-
- var modListener = {};
- var thisCalendar = this;
-
- var modifiedItem = getIcsService().createIcalComponent("VCALENDAR");
- calSetProdidVersion(modifiedItem);
- modifiedItem.addSubcomponent(aNewItem.icalComponent);
- if (aNewItem.recurrenceInfo) {
- var exceptions = aNewItem.recurrenceInfo.getExceptionIds({});
- for each (var exc in exceptions) {
- modifiedItem.addSubcomponent(aNewItem.recurrenceInfo.getExceptionFor(exc, true).icalComponent);
- }
- }
- var modifiedItemICS = modifiedItem.serializeToICS();
-
- modListener.onStreamComplete = function(aLoader, aContext, aStatus,
- aResultLength, aResult) {
- // 201 = HTTP "Created"
- // 204 = HTTP "No Content"
- //
- var status = aContext.responseStatus;
- if (status == 204 || status == 201) {
- LOG("Item modified successfully.");
- var retVal = Components.results.NS_OK;
- // Some CalDAV servers will modify items on PUT (add X-props,
- // change location, etc) so we'd best re-fetch in order to know
- // the current state of the item
- // Observers will be notified in getUpdatedItem()
- thisCalendar.getUpdatedItem(aNewItem, aListener);
- } else if (status == 412) {
- thisCalendar.promptOverwrite(CALDAV_MODIFY_ITEM, aNewItem,
- aListener, aOldItem);
- } else {
- if (status > 999) {
- status = "0x " + status.toString(16);
- }
- LOG("Error modifying item: " + status);
-
- // XXX deal with non-existent item here, other
- // real error handling
-
- // XXX aStatusCode will be 201 Created for a PUT on an item
- // that didn't exist before.
-
- retVal = Components.results.NS_ERROR_FAILURE;
- }
- return;
- }
-
- // XXX use etag as generation
-
- var httpchannel = this.prepChannel(eventUri, modifiedItemICS,
- "text/calendar; charset=utf-8");
-
- if (!aIgnoreEtag) {
- httpchannel.setRequestHeader("If-Match",
- this.mItemInfoCache[aNewItem.id].etag,
- false);
- }
-
- LOG("modifyItem: PUTting = " + modifiedItemICS);
- var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
- .createInstance(Components.interfaces
- .nsIStreamLoader);
-
- if (isOnBranch) {
- streamLoader.init(httpchannel, modListener, httpchannel);
- } else {
- streamLoader.init(modListener);
- httpchannel.asyncOpen(streamLoader, httpchannel);
- }
-
- return;
- },
-
- /**
- * deleteItem(); required by calICalendar.idl
- * the actual deletion is done in doDeleteItem()
- *
- * @param aItem item to delete
- * @param aListener listener for method completion
- */
- deleteItem: function caldavDI(aItem, aListener) {
- return this.doDeleteItem(aItem, aListener, false);
- },
-
- /**
- * Deletes item from CalDAV store.
- *
- * @param aItem item to delete
- * @param aListener listener for method completion
- * @param aIgnoreEtag ignore item etag
- */
- doDeleteItem: function caldavDDI(aItem, aListener, aIgnoreEtag) {
-
- if (aItem.id == null) {
- if (aListener)
- aListener.onOperationComplete (this.superCalendar,
- Components.results.NS_ERROR_FAILURE,
- aListener.DELETE,
- aItem.id,
- "ID doesn't exist for deleteItem");
- return;
- }
-
- var eventUri = this.mCalendarUri.clone();
- eventUri.spec = this.makeUri(this.mItemInfoCache[aItem.id].locationPath);
-
- var delListener = {};
- var thisCalendar = this;
- var realListener = aListener; // need to access from callback
-
- delListener.onStreamComplete =
- function caldavDLoSC(aLoader, aContext, aStatus, aResultLength, aResult) {
-
- var status = aContext.responseStatus;
- // 204 = HTTP "No content"
- //
- if (status == 204) {
- thisCalendar.mMemoryCalendar.deleteItem(aItem, aListener);
- delete thisCalendar.mHrefIndex[eventUri.path];
- delete thisCalendar.mItemInfoCache[aItem.id];
- LOG("Item deleted successfully.");
- var retVal = Components.results.NS_OK;
- }
- else if (status == 412) {
- // item has either been modified or deleted by someone else
- // check to see which
-
- var httpchannel2 = thisCalendar.prepChannel(eventUri, null, null);
- httpchannel2.requestMethod = "HEAD";
- var streamLoader2 = Components.classes
- ["@mozilla.org/network/stream-loader;1"]
- .createInstance(Components.interfaces
- .nsIStreamLoader);
- if (isOnBranch) {
- streamLoader2.init(httpchannel2, delListener2, httpchannel2);
- } else {
- streamLoader2.init(streamListener2);
- httpchannel2.asyncOpen(streamLoader2, httpchannel2);
- }
-
- } else {
- LOG("Error deleting item: " + status);
- // XXX real error handling here
- retVal = Components.results.NS_ERROR_FAILURE;
- }
- }
- var delListener2 = {};
- delListener2.onStreamComplete =
- function caldavDL2oSC(aLoader, aContext, aStatus, aResultLength, aResult) {
- var status2 = aContext.responseStatus;
- if (status2 == 404) {
- // someone else already deleted it
- return;
- } else {
- thisCalendar.promptOverwrite(CALDAV_DELETE_ITEM, aItem,
- realListener, null);
- }
- }
-
- // XXX check generation
- var httpchannel = this.prepChannel(eventUri, null, null);
- if (!aIgnoreEtag) {
- httpchannel.setRequestHeader("If-Match",
- this.mItemInfoCache[aItem.id].etag,
- false);
- }
- httpchannel.requestMethod = "DELETE";
-
- var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
- .createInstance(Components.interfaces
- .nsIStreamLoader);
-
- if (isOnBranch) {
- streamLoader.init(httpchannel, delListener, httpchannel);
- } else {
- streamLoader.init(delListener);
- httpchannel.asyncOpen(streamLoader, httpchannel);
- }
-
- return;
- },
-
- /**
- * Retrieves a specific item from the CalDAV store.
- * Use when an outdated copy of the item is in hand.
- *
- * @param aItem item to fetch
- * @param aListener listener for method completion
- */
- getUpdatedItem: function caldavGUI(aItem, aListener) {
-
- if (aItem == null) {
- if (aListener) {
- aListener.onOperationComplete(this.superCalendar,
- Components.results.NS_ERROR_FAILURE,
- aListener.GET,
- null,
- "passed in null item");
- }
- return;
- }
-
- var itemType = "VEVENT";
- if (aItem instanceof Components.interfaces.calITodo) {
- itemType = "VTODO";
- }
-
- var queryStatuses = new Array();
-
- var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
- var D = new Namespace("D", "DAV:");
- default xml namespace = C;
-
- queryXml =
- <calendar-query xmlns:D="DAV:">
- <D:prop>
- <D:getetag/>
- <calendar-data/>
- </D:prop>
- <filter>
- <comp-filter name="VCALENDAR">
- <comp-filter name={itemType}>
- <prop-filter name="UID">
- <text-match collation="i;octet">
- {aItem.id}
- </text-match>
- </prop-filter>
- </comp-filter>
- </comp-filter>
- </filter>
- </calendar-query>;
-
- this.reportInternal(xmlHeader + queryXml.toXMLString(), aItem, aListener);
- return;
-
- },
-
- // void getItem( in string id, in calIOperationListener aListener );
- getItem: function (aId, aListener) {
- this.mMemoryCalendar.getItem(aId, aListener);
- return;
- },
-
- reportInternal: function caldavRI(aQuery, aItem, aListener)
- {
- var reportListener = new WebDavListener();
- var thisCalendar = this; // need to access from inside the callback
-
- reportListener.onOperationDetail = function(aStatusCode, aResource,
- aOperation, aDetail,
- aClosure) {
- var rv;
- var errString;
-
- // is this detail the entire search result, rather than a single
- // detail within a set?
- //
- if (aResource.path == calendarDirUri.path) {
- // XXX is this even valid? what should we do here?
- // XXX if it's an error, it might be valid?
- LOG("XXX report result for calendar, not event\n");
- throw("XXX report result for calendar, not event\n");
- }
-
- var items = null;
-
- // XXX need to do better than looking for just 200
- if (aStatusCode == 200) {
-
- // aDetail is the response element from the multi-status
- // XXX try-catch
- var xSerializer = Components.classes
- ['@mozilla.org/xmlextras/xmlserializer;1']
- .getService(Components.interfaces.nsIDOMSerializer);
- // libical needs to see \r\n instead on \n\n in the case of "folded" lines
- var response = xSerializer.serializeToString(aDetail).replace(/\n\n/g, "\r\n");
- var responseElement = new XML(response);
-
- // create calIItemBase from e4x object
- // XXX error-check that we only have one result, etc
- var C = new Namespace("urn:ietf:params:xml:ns:caldav");
- var D = new Namespace("DAV:");
-
- var etag = responseElement..D::["getetag"];
-
- // cause returned data to be parsed into the item
- var calData = responseElement..C::["calendar-data"];
- if (!calData.toString().length) {
- Components.utils.reportError(
- "Empty or non-existent <calendar-data> element returned" +
- " by CalDAV server for URI <" + aResource.spec +
- ">; ignoring");
- return;
- }
- // LOG("item result = \n" + calData);
- var rootComp = getIcsService().parseICS(calData, null);
-
- var calComp;
- if (rootComp.componentType == 'VCALENDAR') {
- calComp = rootComp;
- } else {
- calComp = rootComp.getFirstSubcomponent('VCALENDAR');
- }
-
- var unexpandedItems = [];
- var uid2parent = {};
- var excItems = [];
-
- while (calComp) {
- // Get unknown properties
- var prop = calComp.getFirstProperty("ANY");
- while (prop) {
- thisCalendar.unmappedProperties.push(prop);
- prop = calComp.getNextProperty("ANY");
- }
-
- var subComp = calComp.getFirstSubcomponent("ANY");
- while (subComp) {
- // Place each subcomp in a try block, to hopefully get as
- // much of a bad calendar as possible
- try {
- var item = null;
- switch (subComp.componentType) {
- case "VEVENT":
- item = Components.classes["@mozilla.org/calendar/event;1"].
- createInstance(Components.interfaces.calIEvent);
- break;
- case "VTODO":
- item = Components.classes["@mozilla.org/calendar/todo;1"].
- createInstance(Components.interfaces.calITodo);
- break;
- case "VTIMEZONE":
- // we should already have this, so there's no need to
- // do anything with it here.
- break;
- default:
- thisCalendar.unmappedComponents.push(subComp);
- break;
- }
- if (item != null) {
-
- item.icalComponent = subComp;
- // save the location name in case we need to modify
- // need to build using thisCalendar since aResource.spec
- // won't contain any auth info embedded in the URI
- var locationPath = decodeURIComponent(aResource.path)
- .substr(thisCalendar.mLocationPath.length);
- if (!thisCalendar.mItemInfoCache[item.id]) {
- thisCalendar.mItemInfoCache[item.id] = {};
- }
- thisCalendar.mItemInfoCache[item.id].locationPath =
- locationPath;
- thisCalendar.mHrefIndex[aResource.path] = item.id;
- var rid = item.recurrenceId;
- if (rid == null) {
- unexpandedItems.push( item );
- if (item.recurrenceInfo != null) {
- uid2parent[item.id] = item;
- }
- } else {
- item.calendar = thisCalendar.superCalendar;
- // force no recurrence info so we can
- // rebuild it cleanly below
- item.recurrenceInfo = null;
- excItems.push(item);
- }
- }
- } catch (ex) {
- thisCalendar.mObservers.notify("onError", [ex.result, ex.toString()]);
- }
- subComp = calComp.getNextSubcomponent("ANY");
- }
- calComp = rootComp.getNextSubcomponent('VCALENDAR');
- }
-
- // tag "exceptions", i.e. items with rid:
- for each (var item in excItems) {
- var parent = uid2parent[item.id];
- if (parent == null) {
- LOG( "no parent item for rid=" + item.recurrenceId );
- } else {
- item.parentItem = parent;
- item.parentItem.recurrenceInfo.modifyException(item);
- }
- }
- // if we loop over both excItems and unexpandedItems using 'item'
- // we can be confident that 'item' means something below
- for each (var item in unexpandedItems) {
- item.calendar = thisCalendar.superCalendar;
- }
-
- thisCalendar.mItemInfoCache[item.id].etag = etag;
-
- // figure out what type of item to return
- var iid;
- if (item instanceof Components.interfaces.calIEvent) {
- iid = Components.interfaces.calIEvent;
- rv = Components.results.NS_OK;
- items = [ item ];
- } else if (item instanceof Components.interfaces.calITodo) {
- iid = Components.interfaces.calITodo;
- rv = Components.results.NS_OK;
- items = [ item ];
- } else {
- errString = "Can't deduce item type based on query";
- rv = Components.results.NS_ERROR_FAILURE;
- }
-
- } else {
- // XXX
- LOG("aStatusCode = " + aStatusCode);
- errString = "XXX";
- rv = Components.results.NS_ERROR_FAILURE;
- }
-
- if (errString) {
- LOG("errString = " + errString);
- }
-
- for (var i = 0; i < items.length; i++) {
- if (thisCalendar.mItemInfoCache[items[i].id]) {
- thisCalendar.mMemoryCalendar.modifyItem(items[i], null,
- aListener);
- } else {
- thisCalendar.mMemoryCalendar.adoptItem(items[i], aListener);
- }
- }
- return;
- };
-
- reportListener.onOperationComplete = function(aStatusCode, aResource,
- aOperation, aClosure) {
- LOG("refresh completed with status " + aStatusCode);
- thisCalendar.mObservers.notify("onLoad", [thisCalendar]);
- };
-
- // convert this into a form the WebDAV service can use
- var xParser = Components.classes['@mozilla.org/xmlextras/domparser;1']
- .getService(Components.interfaces.nsIDOMParser);
- queryDoc = xParser.parseFromString(aQuery, "application/xml");
-
- // construct the resource we want to search against
- var calendarDirUri = this.mCalendarUri.clone();
- calendarDirUri.spec = this.makeUri('');
- // LOG("report uri = " + calendarDirUri.spec);
- var calendarDirResource = new WebDavResource(calendarDirUri);
-
- var webSvc = Components.classes['@mozilla.org/webdav/service;1']
- .getService(Components.interfaces.nsIWebDAVService);
- webSvc.report(calendarDirResource, queryDoc, true, reportListener,
- this, null);
- return;
-
- },
-
- // void getItems( in unsigned long aItemFilter, in unsigned long aCount,
- // in calIDateTime aRangeStart, in calIDateTime aRangeEnd,
- // in calIOperationListener aListener );
- getItems: function caldav_getItems(aItemFilter, aCount, aRangeStart,
- aRangeEnd, aListener) {
-
-
- this.mMemoryCalendar.getItems(aItemFilter, aCount, aRangeStart,
- aRangeEnd, aListener);
- },
-
- refresh: function caldav_refresh() {
- if (this.mAuthScheme != "Digest") {
- // Basic HTTP Auth will not have timed out, we can just refresh
- // Same for Cosmo ticket-based authentication
- this.safeRefresh();
- } else {
- // Digest auth may have timed out, and we need to make sure that
- // several calendars in this realm do not attempt re-auth simultaneously
- if (this.firstInRealm()) {
- this.safeRefresh();
- }
- }
- },
-
- firstInRealm: function caldav_firstInRealm() {
- var calendars = getCalendarManager().getCalendars({});
- for (var i = 0; i < calendars.length ; i++) {
- if (calendars[i].type != "caldav") {
- continue;
- }
- if (calendars[i].uri.prePath == this.uri.prePath &&
- calendars[i].QueryInterface(calICalDavCalendar)
- .authRealm == this.mAuthRealm) {
- if (calendars[i].id == this.id) {
- return true;
- }
- break;
- }
- }
- return false;
- },
-
- refreshOtherCals: function caldav_refreshOtherCals() {
- var calendars = getCalendarManager().getCalendars({});
- for (var i = 0; i < calendars.length ; i++) {
- if (calendars[i].type == "caldav" &&
- calendars[i].uri.prePath == this.uri.prePath &&
- calendars[i].QueryInterface(calICalDavCalendar)
- .authRealm == this.mAuthRealm &&
- calendars[i].id != this.id) {
- calendars[i].safeRefresh();
- }
- }
- },
-
- safeRefresh: function caldav_safeRefresh() {
-
- var itemTypes = new Array("VEVENT", "VTODO");
- var typesCount = itemTypes.length;
- var refreshEvent = {};
- refreshEvent.itemTypes = itemTypes;
- refreshEvent.typesCount = typesCount;
- refreshEvent.queryStatuses = [];
- refreshEvent.itemsNeedFetching = [];
- refreshEvent.itemsReported = [];
-
- this.getUpdatedItems(refreshEvent);
-
- },
-
-
- getUpdatedItems: function caldav_GUIs(aRefreshEvent) {
-
- if (this.mDisabled) {
- // check if maybe our calendar has become available
- this.checkDavResourceType();
- return;
- }
-
- if (aRefreshEvent.itemTypes.length) {
- var itemType = aRefreshEvent.itemTypes.pop();
- } else {
- return;
- }
-
- var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
- var D = new Namespace("D", "DAV:");
- default xml namespace = C;
-
- var queryXml =
- <calendar-query xmlns:D={D}>
- <D:prop>
- <D:getetag/>
- </D:prop>
- <filter>
- <comp-filter name="VCALENDAR">
- <comp-filter/>
- </comp-filter>
- </filter>
- </calendar-query>;
-
- queryXml[0].C::filter.C::["comp-filter"]
- .C::["comp-filter"] =
- <comp-filter name={itemType}/>;
-
-
- var queryString = xmlHeader + queryXml.toXMLString();
-
- var multigetQueryXml =
- <calendar-multiget xmlns:D={D}>
- <D:prop>
- <D:getetag/>
- <calendar-data/>
- </D:prop>
- </calendar-multiget>;
-
- var etagListener = new WebDavListener();
- var thisCalendar = this;
-
- etagListener.onOperationDetail = function(aStatusCode, aResource,
- aOperation, aDetail,
- aClosure) {
- var xSerializer = Components.classes
- ['@mozilla.org/xmlextras/xmlserializer;1']
- .getService(Components.interfaces.nsIDOMSerializer);
- // libical needs to see \r\n instead on \n\n in the case of
- // "folded" lines
- var response = xSerializer.serializeToString(aDetail).
- replace(/\n\n/g, "\r\n");
- var responseElement = new XML(response);
-
- var etag = responseElement..D::["getetag"];
-
- aRefreshEvent.itemsReported.push(aResource.path);
-
- if (thisCalendar.mHrefIndex[aResource.path]) {
- var itemuid = thisCalendar.mHrefIndex[aResource.path];
- if (etag != thisCalendar.mItemInfoCache[itemuid].etag) {
- // we don't have a current copy in cache; fetch the item
- aRefreshEvent.itemsNeedFetching.push(aResource.path);
- }
- } else {
- aRefreshEvent.itemsNeedFetching.push(aResource.path);
- }
- }
-
- etagListener.onOperationComplete = function(aStatusCode, aResource,
- aOperation, aClosure) {
- aRefreshEvent.queryStatuses.push(aStatusCode);
- var needsRefresh = false;
- if (aRefreshEvent.queryStatuses.length == aRefreshEvent.typesCount) {
-
- for each (var statusCode in aRefreshEvent.queryStatuses) {
- if (statusCode != 207) { // XXX better error checking
- LOG("error fetching item etags: " + statusCode);
- }
- }
-
- // if an item has been deleted from the server, delete it here too
- for (var path in thisCalendar.mHrefIndex) {
- if (aRefreshEvent.itemsReported.indexOf(path) < 0) {
-
- var getItemListener = {};
- getItemListener.onGetResult = function caldav_gUIs_oGR(aCalendar,
- aStatus, aItemType, aDetail, aCount, aItems) {
- var itemToDelete = aItems[0];
- delete thisCalendar.mItemInfoCache[itemToDelete.id];
- thisCalendar.mMemoryCalendar.deleteItem(itemToDelete,
- getItemListener);
- delete thisCalendar.mHrefIndex[path];
- needsRefresh = true;
- }
- getItemListener.onOperationComplete = function
- caldav_gUIs_oOC(aCalendar, aStatus, aOperationType,
- aId, aDetail) {}
- thisCalendar.mMemoryCalendar.getItem(thisCalendar.mHrefIndex[path],
- getItemListener);
- }
- }
-
- // avoid sending empty multiget requests
- // update views if something has been deleted server-side
- if (!aRefreshEvent.itemsNeedFetching.length) {
- if (needsRefresh) {
- thisCalendar.mObservers.notify("onLoad", [thisCalendar]);
- }
- return;
- }
-
- while (aRefreshEvent.itemsNeedFetching.length > 0) {
- var locpath = aRefreshEvent.itemsNeedFetching.pop();
- var hrefXml = new XML();
- hrefXml = <hr xmlns:D={D}/>
- hrefXml.D::href = locpath;
- multigetQueryXml[0].appendChild(hrefXml.D::href);
- }
-
- var multigetQueryString = xmlHeader +
- multigetQueryXml.toXMLString();
- thisCalendar.reportInternal(multigetQueryString, null, null);
-
- if (thisCalendar.mAuthScheme == "Digest" &&
- thisCalendar.firstInRealm()) {
- thisCalendar.refreshOtherCals();
- }
-
- } else {
- thisCalendar.getUpdatedItems(aRefreshEvent);
- }
- }
- var xParser = Components.classes['@mozilla.org/xmlextras/domparser;1']
- .getService(Components.interfaces.nsIDOMParser);
- queryDoc = xParser.parseFromString(queryString, "application/xml");
-
- // construct the resource we want to search against
- var calendarDirUri = this.mCalendarUri.clone();
- calendarDirUri.spec = this.makeUri('');
- var calendarDirResource = new WebDavResource(calendarDirUri);
-
- var webSvc = Components.classes['@mozilla.org/webdav/service;1']
- .getService(Components.interfaces.nsIWebDAVService);
- webSvc.report(calendarDirResource, queryDoc, true, etagListener,
- this, null);
- return;
- },
-
- // nsIInterfaceRequestor impl
- getInterface: function(iid) {
- if (iid.equals(Components.interfaces.nsIAuthPrompt)) {
- return new calAuthPrompt();
- }
- else if (iid.equals(Components.interfaces.nsIPrompt)) {
- // use the window watcher service to get a nsIPrompt impl
- return Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
- .getService(Components.interfaces.nsIWindowWatcher)
- .getNewPrompter(null);
- } else if (iid.equals(Components.interfaces.nsIProgressEventSink)) {
- return this;
- // Needed for Lightning on branch vvv
- } else if (iid.equals(Components.interfaces.nsIDocShellTreeItem)) {
- return this;
- } else if (iid.equals(Components.interfaces.nsIAuthPromptProvider)) {
- return Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
- .getService(Components.interfaces.nsIWindowWatcher)
- .getNewPrompter(null);
- } else if (!isOnBranch && iid.equals(Components.interfaces.nsIAuthPrompt2)) {
- return new calAuthPrompt();
- }
- throw Components.results.NS_ERROR_NO_INTERFACE;
- },
-
- //
- // Helper functions
- //
-
- // Unless an error number is in this array, we consider it very bad, set
- // the calendar to readOnly, and give up.
- acceptableErrorNums: [],
-
- onError: function caldav_onError(aErrNo, aMessage) {
- var errorIsOk = false;
- for each (num in this.acceptableErrorNums) {
- if (num == aErrNo) {
- errorIsOk = true;
- break;
- }
- }
- if (!errorIsOk) {
- this.mReadOnly = true;
- this.mDisabled = true;
- }
-
- var paramBlock = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
- .createInstance(Components.interfaces
- .nsIDialogParamBlock);
- paramBlock.SetNumberStrings(3);
-
- var promptMessage = calGetString("calendar", "disabledMode", [this.name]);
- paramBlock.SetString(0, promptMessage);
- var errCode = "0x"+aErrNo.toString(16);
- paramBlock.SetString(1, errCode);
- paramBlock.SetString(2, aMessage);
- var wWatcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
- .getService(Components.interfaces.nsIWindowWatcher);
- wWatcher.openWindow(null,
- "chrome://calendar/content/calErrorPrompt.xul",
- "_blank",
- "chrome,dialog=yes",
- paramBlock);
-
- if (this.mDisabled) {
- this.refresh();
- }
- },
-
- /**
- * Checks that the calendar URI exists and is a CalDAV calendar
- *
- */
- checkDavResourceType: function checkDavResourceType() {
- var resourceTypeXml = null;
- var resourceType = kDavResourceTypeNone;
- var thisCalendar = this;
-
- var D = new Namespace("D", "DAV:");
- var queryXml = <D:propfind xmlns:D="DAV:">
- <D:prop>
- <D:resourcetype/>
- </D:prop>
- </D:propfind>;
-
- var httpchannel = this.prepChannel(this.mUri,queryXml,
- "text/xml; charset=utf-8");
- httpchannel.setRequestHeader("Depth", "0", false);
- httpchannel.requestMethod = "PROPFIND";
-
- var streamListener = {};
-
- streamListener.onStreamComplete =
- function checkDavResourceType_oSC(aLoader, aContext, aStatus,
- aResultLength, aResult) {
- var resultConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Components
- .interfaces.nsIScriptableUnicodeConverter);
-
- var wwwauth = aContext.getRequestHeader("Authorization");
-
- if (this.mUriParams) {
- thisCalendar.mAuthScheme = "Ticket";
- } else {
- thisCalendar.mAuthScheme = wwwauth.split(" ")[0];
- }
-
- // we only really need the authrealm for Digest auth
- // since only Digest is going to time out on us
- if (thisCalendar.mAuthScheme == "Digest") {
- var realmChop = wwwauth.split("realm=\"")[1];
- thisCalendar.mAuthRealm = realmChop.split("\", ")[0];
- }
-
- resultConverter.charset = "UTF-8";
- var str;
- try {
- str = resultConverter.convertFromByteArray(aResult, aResultLength);
- } catch(e) {
- LOG("Failed to determine resource type");
- }
- str = str.substring(str.indexOf('\n'));
- var multistatus = new XML(str);
-
- var resourceTypeXml = multistatus..D::["resourcetype"];
- if (resourceTypeXml.length == 0) {
- resourceType = kDavResourceTypeNone;
- } else if (resourceTypeXml.toString().indexOf("calendar") != -1) {
- resourceType = kDavResourceTypeCalendar;
- } else if (resourceTypeXml.toString().indexOf("collection") != -1) {
- resourceType = kDavResourceTypeCollection;
- }
-
- if ((resourceType == null || resourceType == kDavResourceTypeNone) &&
- !thisCalendar.mDisabled) {
- thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_NOT_DAV,
- "dav_notDav");
- }
-
- if ((resourceType == kDavResourceTypeCollection) &&
- !thisCalendar.mDisabled) {
- thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_DAV_NOT_CALDAV,
- "dav_davNotCaldav");
- }
-
- // if this calendar was previously offline we want to recover
- if ((resourceType == kDavResourceTypeCalendar) &&
- thisCalendar.mDisabled) {
- thisCalendar.mDisabled = false;
- thisCalendar.mReadOnly = false;
- }
-
- // we've authenticated in the process of PROPFINDing and can flush
- // the getItems request queue
- thisCalendar.setCalHomeSet();
- thisCalendar.checkServerCaps();
- }
- var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
- .createInstance(Components.interfaces
- .nsIStreamLoader);
-
- if (isOnBranch) {
- streamLoader.init(httpchannel, streamListener, httpchannel);
- } else {
- streamLoader.init(streamListener);
- httpchannel.asyncOpen(streamLoader, httpchannel);
- }
- },
-
- reportDavError: function caldav_rDE(aErrNo, aMessage) {
- this.onError(aErrNo, calGetString("calendar", aMessage, [this.mUri.spec]));
- },
-
- /**
- * Checks server capabilities
- * currently just calendar-schedule
- *
- */
- checkServerCaps: function caldav_checkServerCaps() {
-
- var homeSet = this.mCalHomeSet.clone();
- var thisCalendar = this;
-
- var httpchannel = this.prepChannel(homeSet, null, null);
-
- httpchannel.requestMethod = "OPTIONS";
-
- var streamListener = {};
-
- streamListener.onStreamComplete =
- function checkServerCaps_oSC(aLoader, aContext, aStatus,
- aResultLength, aResult) {
- var dav = aContext.getResponseHeader("DAV");
-
- if (dav.indexOf("calendar-schedule") != -1) {
- thisCalendar.mHaveScheduling = true;
- // XXX - we really shouldn't register with the fb service
- // if another calendar with the same principal-URL has already
- // done so
- getFreeBusyService().addProvider(thisCalendar);
- thisCalendar.findPrincipalNS();
- } else {
- LOG("Server does not support CalDAV scheduling.");
- thisCalendar.refresh();
- }
- }
-
- var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
- .createInstance(Components.interfaces
- .nsIStreamLoader);
-
- if (isOnBranch) {
- streamLoader.init(httpchannel, streamListener, httpchannel);
- } else {
- streamLoader.init(streamListener);
- httpchannel.asyncOpen(streamLoader, httpchannel);
- }
-
- },
-
- /**
- * Locates the principal namespace
- */
- findPrincipalNS: function caldav_findPrincipalNS() {
-
- var homeSet = this.mCalHomeSet.clone();
- var thisCalendar = this;
-
- var D = new Namespace("D", "DAV:");
- var queryXml = <D:propfind xmlns:D="DAV:">
- <D:prop>
- <D:principal-collection-set/>
- </D:prop>
- </D:propfind>
-
- var httpchannel = this.prepChannel(homeSet, queryXml,
- "text/xml; charset=utf-8");
-
- httpchannel.setRequestHeader("Depth", "0", false);
- httpchannel.requestMethod = "PROPFIND";
-
- var streamListener = {};
-
- streamListener.onStreamComplete =
- function findInOutBoxes_oSC(aLoader, aContext, aStatus,
- aResultLength, aResult) {
- var resultConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Components
- .interfaces.nsIScriptableUnicodeConverter);
-
- resultConverter.charset = "UTF-8";
- var str;
- try {
- str = resultConverter.convertFromByteArray(aResult, aResultLength);
- } catch(e) {
- LOG("Failed to propstat principal namespace");
- }
- str = str.substring(str.indexOf('\n'));
- var multistatus = new XML(str);
- var pnsUri = thisCalendar.mUri.clone();
- var pcs = multistatus..D::["principal-collection-set"]..D::href;
- if (pcs.charAt(pcs.length-1) != '/') {
- pcs += "/";
- }
-
- pnsUri.path = thisCalendar.ensurePath(pcs);
- thisCalendar.mPrincipalsNS = pnsUri;
- thisCalendar.checkPrincipalsNameSpace();
- }
-
- var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
- .createInstance(Components.interfaces
- .nsIStreamLoader);
-
- if (isOnBranch) {
- streamLoader.init(httpchannel, streamListener, httpchannel);
- } else {
- streamLoader.init(streamListener);
- httpchannel.asyncOpen(streamLoader, httpchannel);
- }
- },
-
- /**
- * Checks the principals namespace for scheduling info
- */
- checkPrincipalsNameSpace: function caldav_cPNS() {
-
- var pns = this.mPrincipalsNS.clone();
- var thisCalendar = this;
-
- var homePath = this.mCalHomeSet.path;
- if (homePath.charAt(homePath.length-1) == '/') {
- homePath = homePath.substr(0, homePath.length-1);
- }
-
- var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
- var D = new Namespace("D", "DAV:");
- default xml namespace = C;
-
- var queryXml = <D:principal-property-search xmlns:D="DAV:"
- xmlns:C="urn:ietf:params:xml:ns:caldav">
- <D:property-search>
- <D:prop>
- <C:calendar-home-set/>
- </D:prop>
- <D:match>{homePath}</D:match>
- </D:property-search>
- <D:prop>
- <C:calendar-home-set/>
- <C:calendar-user-address-set/>
- <C:schedule-inbox-URL/>
- <C:schedule-outbox-URL/>
- </D:prop>
- </D:principal-property-search>;
-
- var httpchannel = this.prepChannel(pns, queryXml,
- "text/xml; charset=utf-8");
-
- httpchannel.requestMethod = "REPORT";
-
- var streamListener = {};
-
- streamListener.onStreamComplete =
- function caldav_cPNS_oSC(aLoader, aContext, aStatus,
- aResultLength, aResult) {
- if (aContext.responseStatus != 207) {
- thisCalendar.mHaveScheduling = false;
- thisCalendar.mInBoxUrl = null;
- thisCalendar.mOutBoxUrl = null;
- return;
- }
- var resultConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Components
- .interfaces.nsIScriptableUnicodeConverter);
-
- resultConverter.charset = "UTF-8";
- var str;
- try {
- str = resultConverter.convertFromByteArray(aResult, aResultLength);
- } catch(e) {
- LOG("Failed to report principals namespace");
- }
- thisCalendar.mMailToUrl = thisCalendar.mCalendarUri.spec;
-
- if (str.substr(0,6) == "<?xml ") {
- str = str.substring(str.indexOf('\n'));
- }
- var multistatus = new XML(str);
-
- for (var i = 0; i < multistatus.*.length(); i++) {
- var response = new XML(multistatus.*[i]);
-
- var responseCHS = response..C::["calendar-home-set"]..D::href[0];
- if (!responseCHS) {
- responseCHS = response..D::["calendar-home-set"]..D::href[0];
- }
-
- if (responseCHS.charAt(responseCHS.toString().length -1) != "/") {
- responseCHS += "/";
- }
-
- if (responseCHS != thisCalendar.mCalHomeSet.path &&
- responseCHS != thisCalendar.mCalHomeSet.spec) {
- continue;
- }
- var addrHrefs =
- response..C::["calendar-user-address-set"]..D::href;
- if (!addrHrefs.toString().length) {
- var addrHrefs =
- response..D::propstat..D::["calendar-user-address-set"]..D::href;
- }
- for (var j = 0; j < addrHrefs.*.length(); j++) {
- if (addrHrefs[j].substr(0,7).toLowerCase() == "mailto:") {
- thisCalendar.mMailToUrl = addrHrefs[j];
- }
- }
- var ibUrl = thisCalendar.mUri.clone();
- var ibPath =
- response..C::["schedule-inbox-URL"]..D::href[0];
- if (!ibPath) {
- var ibPath = response..D::["schedule-inbox-URL"]..D::href[0];
- }
- ibUrl.path = thisCalendar.ensurePath(ibPath);
- thisCalendar.mInBoxUrl = ibUrl;
- var obUrl = thisCalendar.mUri.clone();
- var obPath =
- response..C::["schedule-outbox-URL"]..D::href[0];
- if (!obPath) {
- var obPath = response..D::["schedule-outbox-URL"]..D::href[0];
- }
- obUrl.path = thisCalendar.ensurePath(obPath);
- thisCalendar.mOutBoxUrl = obUrl;
- }
- thisCalendar.refresh();
- }
-
- var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
- .createInstance(Components.interfaces
- .nsIStreamLoader);
-
- if (isOnBranch) {
- streamLoader.init(httpchannel, streamListener, httpchannel);
- } else {
- streamLoader.init(streamListener);
- httpchannel.asyncOpen(streamLoader, httpchannel);
- }
- return;
- },
-
- //
- // calIFreeBusyProvider interface
- //
-
- getFreeBusyIntervals: function caldav_getFreeBusyIntervals(
- aCalId, aRangeStart, aRangeEnd, aBusyTypes, aListener) {
-
- if (!this.mHaveScheduling || !this.mOutBoxUrl || !this.mMailToUrl) {
- LOG("Server does not support scheduling; freebusy query not possible");
- return;
- }
-
- // the caller prepends MAILTO: to calid strings containing @
- // but apple needs that to be mailto:
- var aCalIdParts = aCalId.split(":");
- aCalIdParts[0] = aCalIdParts[0].toLowerCase();
-
- if (aCalIdParts[0] != "mailto"
- && aCalIdParts[0] != "http"
- && aCalIdParts[0] != "https" ) {
- return;
- }
- mailto_aCalId = aCalIdParts.join(":");
-
- var outBoxUri = this.mOutBoxUrl.clone();
- var thisCalendar = this;
-
- var organizer = this.mMailToUrl;
-
- var dtstamp = now().getInTimezone(UTC()).icalString;
- var dtstart = aRangeStart.getInTimezone(UTC()).icalString;
- var dtend = aRangeEnd.getInTimezone(UTC()).icalString;
- var uuid = getUUID();
-
- var fbQuery = "BEGIN:VCALENDAR\n";
- fbQuery += "VERSION:" + calGetProductVersion() + "\n";
- fbQuery += "PRODID:-" + calGetProductId() + "\n";
- fbQuery += "METHOD:REQUEST\n";
- fbQuery += "BEGIN:VFREEBUSY\n";
- fbQuery += "DTSTAMP:" + dtstamp + "\n";
- fbQuery += "ORGANIZER:" + organizer + "\n";
- fbQuery += "DTSTART:" + dtstart + "\n";
- fbQuery += "DTEND:" + dtend + "\n";
- fbQuery += "UID:" + uuid + "\n";
- var attendee = "ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL;CN=" + mailto_aCalId + "\n";
- var attendeeFolded = this.foldLine(attendee);
- fbQuery += attendeeFolded;
- fbQuery += "END:VFREEBUSY\n";
- fbQuery += "END:VCALENDAR\n";
- // RFC 2445 is specific about how lines end...
- fbQuery = fbQuery.replace(/\n/g, "\r\n");
-
- var httpchannel = this.prepChannel(outBoxUri, fbQuery,
- "text/calendar; charset=utf-8");
- httpchannel.requestMethod = "POST";
- httpchannel.setRequestHeader("Originator", organizer, false);
- httpchannel.setRequestHeader("Recipient", mailto_aCalId, false);
-
- var streamListener = {};
-
- streamListener.onStreamComplete =
- function caldav_GFBI_oSC(aLoader, aContext, aStatus,
- aResultLength, aResult) {
- var resultConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Components
- .interfaces.nsIScriptableUnicodeConverter);
-
- resultConverter.charset = "UTF-8";
- var str;
- try {
- str = resultConverter.convertFromByteArray(aResult, aResultLength);
- } catch(e) {
- LOG("Failed to parse freebusy response");
- }
-
- if (aContext.responseStatus == 200) { // XXX = better error handling
- var periodsToReturn = [];
- var CalPeriod = new Components.
- Constructor("@mozilla.org/calendar/period;1",
- "calIPeriod");
- var CalDateTime = new Components.
- Constructor("@mozilla.org/calendar/datetime;1",
- "calIDateTime");
- var fbTypeMap = {};
- fbTypeMap["FREE"] = calIFreeBusyInterval.FREE;
- fbTypeMap["BUSY"] = calIFreeBusyInterval.BUSY;
- fbTypeMap["BUSY-UNAVAILABLE"] = calIFreeBusyInterval.BUSY_UNAVAILABLE;
- fbTypeMap["BUSY-TENTATIVE"] = calIFreeBusyInterval.BUSY_TENTATIVE;
- var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
- var D = new Namespace("D", "DAV:");
-
- if (str.substr(0,6) == "<?xml ") {
- str = str.substring(str.indexOf('\n'));
- }
- str = str.replace(/\n\ /g, "");
- str = str.replace(/\r/g, "");
-
- var response = new XML(str);
- var status = response..C::response..C::["request-status"];
- if (status.substr(0,1) != 2) {
- LOG("Got status " + status + " in response to freebusy query");
- return;
- }
- if (status.substr(0,3) != "2.0") {
- LOG("Got status " + status + " in response to freebusy query");
- }
- var caldata = response..C::response..C::["calendar-data"];
- var lines = caldata.split("\n");
- for (var i = 0; i < lines.length; i++) {
- if (lines[i].substr(0,8) == "FREEBUSY") {
- var descDat = lines[i].split(":");
- var fbName = descDat[0].split("=")[1];
- var fbType = fbTypeMap[fbName];
- var ranges = descDat[1].split(",");
- for (var j = 0; j < ranges.length; j++) {
- var parts = ranges[j].split("/");
- var begin = new CalDateTime();
- begin.icalString = parts[0];
- var end = new CalDateTime();
- end.icalString = parts[1];
- var period = new CalPeriod();
- period.start = begin;
- period.end = end;
- period.makeImmutable();
- var interval = {
- QueryInterface: function fbInterval_QueryInterface(iid) {
- ensureIID([calIFreeBusyInterval, nsISupports], iid);
- return this;
- },
- calId: aCalId,
- interval: period,
- freeBusyType: fbType
- };
- periodsToReturn.push(interval);
- }
- }
- }
- aListener.onResult(null, periodsToReturn);
- } else {
- LOG("Received status " + aContext.responseStatus + " from freebusy query");
- }
-
- }
-
- var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
- .createInstance(Components.interfaces
- .nsIStreamLoader);
-
- if (isOnBranch) {
- streamLoader.init(httpchannel, streamListener, httpchannel);
- } else {
- streamLoader.init(streamListener);
- httpchannel.asyncOpen(streamLoader, httpchannel);
- }
-
- },
-
- /**
- * RFC 2445 line folding
- */
- foldLine: function caldav_foldLine(aString) {
- var parts = [];
- while (aString.length) {
- var part = aString.substr(0,72);
- parts.push(part);
- aString = aString.substr(72);
- }
- return parts.join("\n ");
- },
-
- ensurePath: function caldav_ensurePath(aString) {
- if (aString.charAt(0) != "/") {
- var bogusUri = makeURL(aString);
- return bogusUri.path;
- }
- return aString;
- },
-
- // stubs to keep callbacks we don't support yet from throwing errors
- // we don't care about
- // nsIProgressEventSink
- onProgress: function onProgress(aRequest, aContext, aProgress, aProgressMax) {},
- onStatus: function onStatus(aRequest, aContext, aStatus, aStatusArg) {},
- // nsIDocShellTreeItem
- findItemWithName: function findItemWithName(name, aRequestor, aOriginalRequestor) {}
- };
-
- function WebDavResource(url) {
- this.mResourceURL = url;
- }
-
- WebDavResource.prototype = {
- mResourceURL: {},
- get resourceURL() {
- return this.mResourceURL;} ,
- QueryInterface: function(iid) {
- if (iid.equals(CI.nsIWebDAVResource) ||
- iid.equals(CI.nsISupports)) {
- return this;
- }
-
- throw Components.interfaces.NS_ERROR_NO_INTERFACE;
- }
- };
-
- function WebDavListener() {
- }
-
- WebDavListener.prototype = {
-
- QueryInterface: function (aIID) {
- if (!aIID.equals(Components.interfaces.nsISupports) &&
- !aIID.equals(nsIWebDavOperationListener)) {
- throw Components.results.NS_ERROR_NO_INTERFACE;
- }
-
- return this;
- },
-
- onOperationComplete: function(aStatusCode, aResource, aOperation,
- aClosure) {
- // aClosure is the listener
- aClosure.onOperationComplete(this, aStatusCode, 0, null, null);
-
- LOG("WebDavListener.onOperationComplete() called");
- return;
- },
-
- onOperationDetail: function(aStatusCode, aResource, aOperation, aDetail,
- aClosure) {
- LOG("WebDavListener.onOperationDetail() called");
- return;
- }
- }
-
- function calDavObserver(aCalendar) {
- this.mCalendar = aCalendar;
- }
-
- calDavObserver.prototype = {
- mCalendar: null,
- mInBatch: false,
-
- // calIObserver:
- onStartBatch: function() {
- this.mCalendar.observers.notify("onStartBatch");
- this.mInBatch = true;
- },
- onEndBatch: function() {
- this.mCalendar.observers.notify("onEndBatch");
- this.mInBatch = false;
- },
- onLoad: function(calendar) {
- this.mCalendar.observers.notify("onLoad", [calendar]);
- },
- onAddItem: function(aItem) {
- this.mCalendar.observers.notify("onAddItem", [aItem]);
- },
- onModifyItem: function(aNewItem, aOldItem) {
- this.mCalendar.observers.notify("onModifyItem", [aNewItem, aOldItem]);
- },
- onDeleteItem: function(aDeletedItem) {
- this.mCalendar.observers.notify("onDeleteItem", [aDeletedItem]);
- },
- onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
- this.mCalendar.observers.notify("onPropertyChanged", [aCalendar, aName, aValue, aOldValue]);
- },
- onPropertyDeleting: function(aCalendar, aName) {
- this.mCalendar.observers.notify("onPropertyDeleting", [aCalendar, aName]);
- },
-
- // Unless an error number is in this array, we consider it very bad, set
- // the calendar to readOnly, and give up.
- acceptableErrorNums: [],
-
- onError: function(aErrNo, aMessage) {
- var errorIsOk = false;
- for each (num in this.acceptableErrorNums) {
- if (num == aErrNo) {
- errorIsOk = true;
- break;
- }
- }
- if (!errorIsOk)
- this.mCalendar.readOnly = true;
- this.mCalendar.observers.notify("onError", [aErrNo, aMessage]);
- }
- };
-
- var g_fbService = null;
- function getFreeBusyService() {
- if (!g_fbService) {
- g_fbService =
- Components.classes["@mozilla.org/calendar/freebusy-service;1"]
- .getService(Components.interfaces.calIFreeBusyService);
- }
- return g_fbService;
- };
-